
\chapter{性能测试}{}

本节将针对本文提出的通用服务端框架的性能与鲁棒性做了相应的测试。
本节使用压力测试的工具为wrk。wrk是一种现代的Http基准测试工具\textsuperscript{\cite{wrk}}，能够在单个多核cpu上运行时产生显著的负载。它将多线程设计与可伸缩的事件通知系统（如epoll和kqueue）相结合\textsuperscript{\cite{杨家蓉2020WRK}}。

除了对自身的性能做出相应的评估以外，本章还将在各项性能指标上直接对比Apache软件基金会开源Http服务器Tomcat\textsuperscript{\cite{凌质亿2013高并发环境下}}。

\section{模拟cpu密集型上层业务进行压力测试}{}

首先利用一个简单的cpu密集型Http请求进行测试：该请求中服务端仅完成访问次数的累加统计并将其作为Http包体打包发送回客户。

使用wrk运行一个持续2秒的基准测试，使用6个线程，保持100个Http连接打开。首先是将这一Http接口搭建在本文提出的框架之上，测试结果如表\ref{tab:perf_index_unixtar}所示。
\begin{table}[h]
    \caption{\label{tab:perf_index_unixtar}将cpu密集型上层业务搭建于本文框架的测试结果}
    \centering
    \begin{tabular}{c c c c c}
        \toprule
        Thread Stats    & Avg       & Stdev       & Max     &  +/- Stdev    \\
        \midrule
        Latency       & 85.85ms  & 154.19ms & 992.16ms   & 87.93\%    \\
        Req/Sec  	  & 75.85     & 57.81   & 257.00    & 78.26\%   \\
        \bottomrule
    \end{tabular}
\end{table}

随后我们将完全相同的Http请求的实现搭建在Tomcat之上\textsuperscript{\cite{高张2008提高}}，使用相同的压力测试参数，于同一台服务器上进行测试。Tomcat的测试结果如表\ref{tab:perf_index_tomcat}所示。
\begin{table}[h]
    \caption{\label{tab:perf_index_tomcat}将cpu密集型上层业务搭建于Tomcat的测试结果}
    \centering
    \begin{tabular}{c c c c c}
        \toprule
        Thread Stats    & Avg       & Stdev       & Max     &  +/- Stdev    \\
        \midrule
        Latency       & 133.04ms  & 198.19ms   & 1.02s   & 89.89\%    \\
        Req/Sec  	  & 60.62     & 58.32      & 274.00     & 86.02\%   \\
        \bottomrule
    \end{tabular}
\end{table}

对比上表可以看到，在执行cpu密集型任务时，本文提出的服务端框架平均访问时延低比Tomcat低35.47\%，单位时间内完成的请求数比Tomcat高25.12\%，两指标对应的标准偏差也比Tomcat低。



\section{模拟I/O密集型上层业务进行压力测试}{}

我们在一个Http请求的业务逻辑中使用sleep()函数将当前线程睡眠200$ms$，以模拟出耗时的I/O密集型上层业务。

\begin{table}[h]
    \caption{\label{tab:perf_sleep_unixtar}将I/O密集型上层业务搭建于本文框架的测试结果}
    \centering
    \begin{tabular}{c c c c c}
        \toprule
        Thread Stats    & Avg       & Stdev       & Max     &  +/- Stdev    \\
        \midrule
        Latency       & 1.29s       & 457.45ms   & 1.97s    & 61.82\%    \\
        Req/Sec  	  & 4.47        & 3.81       & 19.00    & 72.67\%   \\
        \bottomrule
    \end{tabular}
\end{table}

随后我们将sleep()函数也写进到Tomcat\textsuperscript{\cite{孙仁鹏2012Tomcat}}，使用相同的压力测试参数，于同一台服务器上进行测试。Tomcat的测试结构如表\ref{tab:perf_sleep_tomcat}所示。

\begin{table}[h]
    \caption{\label{tab:perf_sleep_tomcat}将I/O密集型上层业务搭建于Tomcat的测试结果}
    \centering
    \begin{tabular}{c c c c c}
        \toprule
        Thread Stats    & Avg       & Stdev       & Max     &  +/- Stdev    \\
        \midrule
        Latency       & 266.31ms  & 126.17ms   & 1.21s    & 93.12\%    \\
        Req/Sec  	  & 46.00     & 35.91   & 150.00     & 72.97\%   \\
        \bottomrule
    \end{tabular}
\end{table}

对比上表可知，在执行I/O密集型任务时，本文提出的框架相比于Tomcat，平均访问延迟高了3.84倍，单位时间内完成的请求数低了90.28\%，这极大得降低了框架的执行效率。这是一个让本人难以接受的数据。

简要分析可知，在进行这次实验时，我们配置的线程数量分别是：4个网络线程和4个工作线程。在执行I/O密集型任务时，最多有4个工作线程同时等待我们模拟出的200$ms$的I/O耗时。

由于这是个几乎纯I/O型任务，因此当我们大幅增大工作线程的数量以完成上层业务，也不会降低底层框架所分得的计算资源。同时，I/O密集型任务不会频繁参与cpu调度（如本实验中只有等到200$ms$超时后才会重新调度线程），不会带来过多的上下文切换开销。

修改本文框架的配置文件，调整工作线程的数量到100线程，重新进行实验。结果如表\ref{tab:perf_sleep_unixtar_100worker}所示。

\begin{table}[h]
    \caption{\label{tab:perf_sleep_unixtar_100worker}优化（定制工作线程数量）后，将I/O密集型任务搭建于本文框架的测试结果}
    \centering
    \begin{tabular}{c c c c c}
        \toprule
        Thread Stats    & Avg       & Stdev       & Max     &  +/- Stdev    \\
        \midrule
        Latency       & 251.38ms   & 49.34ms    & 467.40ms   & 88.52\%    \\
        Req/Sec  	  & 56.74      & 32.86      & 141.00     & 62.86\%   \\
        \bottomrule
    \end{tabular}
\end{table}

重新对比Tomcat测试数据可知，本文提出的服务端框架性能数据大幅改善。其中，平均访问时延低比Tomcat低5.61\%，单位时间内完成的请求数比Tomcat高23.34\%，二者对应的标准偏差也比Tomcat低。

\section{实验结果分析与展望}{}

在以上两个实验中我们见证到了不同类型的任务配合不同数量的工作线程带来的大幅性能提升。但是，在实现通用服务端框架时，我们无法预知上层业务是I/O密集型还是cpu密集型，因此我们只能配置工作线程数为一个折中值。

在将来，我们会将这一选择开放给应用层程序员——引入协程机制。在处理I/O密集型的上层业务时，使用协程的方式取代线程切换。相比与线程切换，协程通过让出当前cpu的方式，修改程序计数器的值跳转到其他的指令序列入口，直接在用户层完成上下文切换，具有更小的上下文切换代价。底层框架也不再需要提前评估上层业务来动态配置工作线程数量。

